/*
 * BaseStation.java
 */
package rehab.host.spotfinder;

import com.sun.spot.io.j2me.radiogram.RadiogramConnection;
import com.sun.spot.peripheral.radio.IRadioPolicyManager;
import com.sun.spot.peripheral.radio.RadioFactory;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.microedition.io.Connector;
import javax.microedition.io.Datagram;
import rehab.host.conn.Listener;
import rehab.host.global.PacketTypes;
import rehab.host.global.ProtocolParameter;
import rehab.host.global.SensorTypes;
import rehab.host.global.SingleSPOT;
import rehab.host.gui.MainFrame;

/**
 * @author mi zhang
 */
public class BaseStation implements SensorTypes, ProtocolParameter, PacketTypes {

    /**
     * Local Variables.
     */
    //n
    private MainFrame myGUI = null;
    //
    private Vector mySPOTLIST = null;                   // Vector itself is synchronized
    //
    private BitMap PORTBitMap = null;                   // Shared Variable. Need synchronization
    //
    private RadiogramConnection rcvConn = null;
    // Thread Variables
    private Thread heartbeatListenerThread = null;
    // Loop Control
    private boolean heartbeatListenerDo = false;
    //
    private static final int MAX_SPOT_NUMBER        = 10;

    private boolean baseStationPresent = false;

    public Listener myListener;
    /**
     * =========================================================================
     */

    /**
     * Pause for a specified time.
     *
     * @param time the number of milliseconds to pause
     */
    public void pause (long time) {
        try {
            Thread.currentThread().sleep(time);
        } catch (InterruptedException ex) {
            System.err.println("Caught " + ex + " while pausing.");
        }
    }

    /**
     * Initialize any needed variables.
     */
    public void initialize() {
        // Radio setup
        init();
        if(baseStationPresent){
        IRadioPolicyManager rpm = RadioFactory.getRadioPolicyManager();
        rpm.setChannelNumber(channel);
        rpm.setPanId(PAN_ID);
        rpm.setOutputPower(power - 32); // "power = 0" is the maximum power allowed for SPOT
        pause(SECOND);
        // Local variables initialization
        heartbeatListenerDo = true;
        mySPOTLIST = new Vector();                  // Vector itself is synchronized
        PORTBitMap = new BitMap(MAX_SPOT_NUMBER);   // Shared Variable. Need synchronization
        }else{
            //just get out
        }
    }

    /**
     * Connect to base station & other initialization.
     */
    private void init () {
        RadiogramConnection rcvConn = null;
        try {
            rcvConn = (RadiogramConnection)Connector.open("radiogram://:" + "100");
            baseStationPresent = true;
        } catch (Exception ex) {
            baseStationPresent = false;
            System.out.println("Problem connecting to base station: " + ex);
        } finally {
            try {
                if (rcvConn != null) {
                    rcvConn.close();
                }
            } catch (IOException ex) { /* ignore */ }
        }
    }

    private Listener returnListener(Listener list){
        return list;
    }

    /**
     * "Heartbeat" Packets Receiver and send back ACK
     *      A. Port Number Assignment
     *      B. Generate corresponding Listeners
     * NOTE: No TIMEOUT since BS is always powered on, there is no need
     * for saving energy.
     */
    public void heartbeatListenerLoop () {
        System.out.println("Thread_HBLL: Begins.");
        int debug_counter = 0;
        Datagram rdg = null;
        try {
        /**
         * Step1: Open a server connection to receive the ACK packets.
         */
            rcvConn = (RadiogramConnection) Connector.open("radiogram://:" + HEARTBEAT_BROADCAST_PORT); // Server !
            rdg = rcvConn.newDatagram(rcvConn.getMaximumLength());
        /**
         * Step2: Book-keeping the Free-range SunSPOTs.
         */
            while (heartbeatListenerDo) {
               try {
                    debug_counter++;
                    System.out.println("Loop_HBLL: " + debug_counter + " starts.");
                    rdg.reset();
                    rcvConn.receive(rdg);   // Blocking
                    String SPOTAddress = rdg.getAddress();  // Incoming SPOT's MAC Id
                    int myPort = -1;
                    System.out.println("Loop_HBLL: " + debug_counter + " Incoming Broadcast packet address: " + SPOTAddress + " ...");
                    byte packetType = rdg.readByte();
                    if (packetType == HEARTBEAT_PACKET) {
                        System.out.println("Loop_HBLL: " + debug_counter + " is HEARTBEAT_PACKET.");
                        /**
                         * 1. Check if the LIST contains this SunSPOT.
                         *      a) if there is NOT:
                         *          1. Assign it a new port number
                         *          2. Generate a new listener
                         *          3. Insert the new SPOT into the LIST.
                         *          4. Send the ACK_FOR_HEARTBEAT_PACKET back
                         *      b) if there is:
                         *          1. Send back the ACK_FOR_HEARTBEAT_PACKET again.
                         */
                        boolean isInTheLIST = false;
                        Enumeration enu = mySPOTLIST.elements();    // Local enumerator
                        while (enu.hasMoreElements()) {
                            SingleSPOT next = (SingleSPOT) enu.nextElement();
                            if (next.getAddress().equals(SPOTAddress)) {
                                System.out.println("Loop_HBLL: " + debug_counter + " Address Matches!.");
                                isInTheLIST = true;
                             // It seems ACK_FOR_HEARTBEAT_PACKET packet is lost.
                             // Therefore send back the ACK_FOR_HEARTBEAT_PACKET again.
                                Datagram xdg = rcvConn.newDatagram(rcvConn.getMaximumLength());
                                xdg.reset();
                                xdg.setAddress(rdg);
                                xdg.writeByte(ACK_FOR_HEARTBEAT_PACKET);
                                xdg.writeInt(next.getPort());
                                rcvConn.send(xdg);
                                break;
                           }
                        }
                     /**
                      * If this is a new SPOT,
                      *      1. Find an available port in the BitMap
                      *      2. Generate a new listener
                      *      3. Register the new SPOT into the LIST
                      *      4. Send the ACK_FOR_HEARTBEAT_PACKET back
                      */
                        if (isInTheLIST == false) {
                            System.out.println("Loop_HBLL: " + debug_counter + " Find New SPOT ! ");
                         // 1. Find an available port
                            synchronized (PORTBitMap) {
                                myPort = PORTBitMap.Find();
                            }
                            if (myPort == -1) {
                                System.out.println("Loop_HBLL: " + debug_counter + " No free radio ports available now ");
                                // Enlarge the total length of the BipMap dynamically ??? -- Right now NO
                            }
                            else {
                                myPort = myPort + 60;   // 0 to 31 are reserved for system use
                                System.out.println("Loop_HBLL: " + debug_counter + " New Port: " + myPort);
                             // 2. Generate a new listener
                                myListener = new Listener(myPort, mySPOTLIST, PORTBitMap, myGUI);
                             // 3. Register the new SPOT into the LIST
                                SingleSPOT newMember = new SingleSPOT(SPOTAddress, myPort, myListener);

                                mySPOTLIST.addElement(newMember);
                                System.out.println("Loop_HBLL: " + debug_counter + " The New SPOT is added into the mySPOTLIST");

                             // Start the Listener
                                myListener.start();
                             // 4. Send the ACK_FOR_HEARTBEAT_PACKET back
                                Datagram xdg = rcvConn.newDatagram(rcvConn.getMaximumLength());
                                xdg.reset();
                                xdg.setAddress(rdg);
                                xdg.writeByte(ACK_FOR_HEARTBEAT_PACKET);
                                xdg.writeInt(myPort);
                                rcvConn.send(xdg);
                            }
                        }
                        System.out.println("Loop_HBLL: " + debug_counter + " mySPOTLIST.size() = " + mySPOTLIST.size());
                        //}
                    } else {
                        /* Ignore other kinds of packets */
                        System.out.println("Loop_HBLL: " + debug_counter + " NOT HEARTBEAT_PACKET.");
                    }
                } catch (Exception ex) {
                    System.err.println("Loop_HBLL: Caught " + ex + " in send/receive packets.");
                    ex.printStackTrace();
                }
            }
      } catch (Exception ex) {
         System.err.println("Loop_HBLL: Caught " + ex + " in rcvConn connection initialization.");
         ex.printStackTrace();
         //System.exit(1);
      }
    }
    
    /**
     * Default BaseStation Constructor
     * Called when we start a new session
     * NOTE: myGUI has the MainGUI object
     * 0. Initialization
     * 1. Spawn a thread to broadcast "Heartbeat" packets.
     * 2. Spawn a new thread to listen to "Heartbeat" packets.
     *      A. Port Number Assignment
     *      B. Generate corresponding Listeners
     */
    public BaseStation(MainFrame objGUI){

        System.out.println("BaseStation starts.");
        this.myGUI = objGUI;
     // 0. Initialization
        initialize();
     // 1. Spawn a new thread to listen to "Heartbeat" packets and send back ACK.
     //     A. Port Number Assignment
     //     B. Generate corresponding Listeners
        heartbeatListenerThread = new Thread() {
            //@Override
            public void run () {
                heartbeatListenerLoop();
            }
        };                      
        heartbeatListenerThread.start(); 
    }

    /**
     * Return SPOT List
     * Called when we go to the Device List Panel, or we REFRESH the Device List Panel
     * @return mySPOTLIST;
     */
    public Vector returnSPOTList () {
        pause(3* SECOND);
        //TODO - try to do deepcopy to pass the list to GUI.
        //synchronized (mySPOTLIST) {
            return mySPOTLIST;
        //}
    }

    /**
     * Configure SPOT
     * Called when we configure the SPOT based on its MAC ID
     * 0.
     * @param myAddress
     * @param sensorList
     * @return true: succeed
     *         false: fail
     */
    //public void configureSPOT (String myAddress, Vector sensorList) {
    public boolean configureSPOT (String myAddress) {
        boolean isInTheLIST = false;
        //synchronized (mySPOTLIST) {
            //TODO - can add hashmap
            Enumeration enu = mySPOTLIST.elements();    // Local enumerator
            while (enu.hasMoreElements()) {
                SingleSPOT next = (SingleSPOT) enu.nextElement();
                if (next.getAddress().equals(myAddress)) {
                   System.out.println("Inside configureSPOT: Address Matches!");
                   isInTheLIST = true;
                   // Get the Listener
                   Listener myListener = next.getListener();
                   // Get the sensor from the input sensorList, send the "addSensor" Commends to the Listener

                   //TODO - after
                   //myListener.addSensor(ACCELEROMETER_X_SENSOR);

                   break;
               } else { // Nothing

               }
            }
        //}
        if (isInTheLIST == false) {
            System.out.println("Inside configureSPOT: Can NOT find the SPOT!");
            return false;
        }
        else
            return true;
    }


    /**
     * Start Listener
     * Called when we want the SPOT to start transmitting the data.
     * 0.
     * @param myAddress
     * @return true: succeed
     *         false: fail
     */
    public boolean startListener (String myAddress) {
        boolean isInTheLIST = false;
        //synchronized (mySPOTLIST) {
            Enumeration enu = mySPOTLIST.elements();    // Local enumerator
            while (enu.hasMoreElements()) {
                SingleSPOT next = (SingleSPOT) enu.nextElement();
                if (next.getAddress().equals(myAddress)) {
                   System.out.println("Inside startListener: Address Matches!");
                   isInTheLIST = true;
                   // Get the Listener
                   Listener myListener = next.getListener();
                   // Start the Listener
                   myListener.start();
                   break;
               } else { // Nothing

               }
            }
        //}
        if (isInTheLIST == false) {
            System.out.println("Inside startListener: Can NOT find the SPOT!");
            return false;
        }
        else
            return true;
    }
    
    /**
     * Start All the Listeners
     * Called when we want ALL the SPOTs to start transmitting the data.
     * 0. 
     * @return true: succeed
     *         false: fail
     */
    public boolean startAllListener () {
        //synchronized (mySPOTLIST) {
            Enumeration enu = mySPOTLIST.elements();    // Local enumerator
            while (enu.hasMoreElements()) {
                SingleSPOT next = (SingleSPOT) enu.nextElement();                
                // Get the Listener
                Listener myListener = next.getListener();
                // Start the Listener
                myListener.start();
            }
        //}
        return true;
    }

    /**
     * Stop Listener
     * Called when we want the SPOT to stop transmitting the data.
     * 0.
     * @param myAddress
     * @return true: succeed
     *         false: fail
     */
    public boolean pauseListener (String myAddress) {
        boolean isInTheLIST = false;
        //synchronized (mySPOTLIST) {
            Enumeration enu = mySPOTLIST.elements();    // Local enumerator
            while (enu.hasMoreElements()) {
                SingleSPOT next = (SingleSPOT) enu.nextElement();
                if (next.getAddress().equals(myAddress)) {
                   System.out.println("Inside stopListener: Address Matches!");
                   isInTheLIST = true;
                   // Get the Listener
                   Listener myListener = next.getListener();
                   // Start the Listener
                   myListener.doPause();
                   break;
               } else { // Nothing

               }
            }
        //}
        if (isInTheLIST == false) {
            System.out.println("Inside stopListener: Can NOT find the SPOT!");
            return false;
        }
        else
            return true;
    }

    /**
     * Stop All the Listeners
     * Called when we want ALL the SPOTs to stop transmitting the data.
     * 0.
     * @return true: succeed
     *         false: fail
     */
    public boolean pauseAllListener () {
        //synchronized (mySPOTLIST) {
            Enumeration enu = mySPOTLIST.elements();    // Local enumerator
            while (enu.hasMoreElements()) {
                SingleSPOT next = (SingleSPOT) enu.nextElement();                
                // Get the Listener
                Listener myListener = next.getListener();
                // Start the Listener
                myListener.doPause();
            }
        //}
        return true;
    }

    /**
     * Reclaim Listener
     * Called when we finish the session.
     * 0.
     * @param myAddress 
     * @return true: succeed
     *         false: fail
     */
    public boolean stopListener (String myAddress) {
        boolean isInTheLIST = false;
        //synchronized (mySPOTLIST) {
            Enumeration enu = mySPOTLIST.elements();    // Local enumerator
            while (enu.hasMoreElements()) {
                SingleSPOT next = (SingleSPOT) enu.nextElement();
                if (next.getAddress().equals(myAddress)) {
                   System.out.println("Inside reclaimListener: Address Matches!");
                   isInTheLIST = true;
                    // Get the Listener
                    Listener myListener = next.getListener();
                    // Start the Listener
                    myListener.doStop();
                    // Reclaim the Port
                   synchronized (PORTBitMap) {
                        PORTBitMap.Clear(next.getPort() - 60);
                   }
                   System.out.println("Inside reclaimListener: Port#" + (next.getPort() - 60) + "is cleared");
                   //Delete the SPOT from the SPOTLIST
                   mySPOTLIST.removeElement(next);
                   break;
               } else { // Nothing

               }
            }
        //}
        if (isInTheLIST == false) {
            System.out.println("Inside reclaimListener: Can NOT find the SPOT!");
            return false;
        }
        else
            return true;
    }

    /**
     * Reclaim All the Listeners
     * Called when we finish the session.
     * 0.
     * @return true: succeed
     *         false: fail
     */
    public boolean stopAllListener () {
        //synchronized (mySPOTLIST) {
            Enumeration enu = mySPOTLIST.elements();    // Local enumerator
            while (enu.hasMoreElements()) {
                SingleSPOT next = (SingleSPOT) enu.nextElement();                
                // Get the Listener
                Listener myListener = next.getListener();
                // Start the Listener
                myListener.doStop();
                // Reclaim the Port
                synchronized (PORTBitMap) {
                    PORTBitMap.Clear(next.getPort() - 60);
                }
                System.out.println("Inside reclaimListener: Port#" + (next.getPort() - 60) + "is cleared");
             // Delete the SPOT from the SPOTLIST
                mySPOTLIST.removeElement(next);
            }
        //}
        return true;
    }

    /**
     * Stop the BastStation
     * Called when we finish the session.
     */
     public void stopBaseStation () {
        try {
            heartbeatListenerDo = false;
            rcvConn.close();
            System.out.println("BaseStation finishes.");
        } catch (IOException ex) {
            Logger.getLogger(BaseStation.class.getName()).log(Level.SEVERE, null, ex);
        }
     }
}